package com.chensi.admin.web.service.impl;

import com.chensi.admin.web.common.Constants;
import com.chensi.admin.web.domain.SysUserToken;
import com.chensi.admin.web.domain.WebMenu;
import com.chensi.admin.web.domain.WebUser;
import com.chensi.admin.web.exception.BaseException;
import com.chensi.admin.web.exception.UserLockedException;
import com.chensi.admin.web.oauth2.OAuth2Token;
import com.chensi.admin.web.oauth2.TokenGenerator;
import com.chensi.admin.web.service.WebMenuService;
import com.chensi.admin.web.service.WebUserService;
import com.chensi.admin.web.util.RedisUtil;
import com.chensi.admin.web.util.ValidationUtil;
import com.chensi.admin.web.dao.SysUserTokenRepository;
import com.chensi.admin.web.dao.WebUserRepository;
import com.chensi.admin.web.dto.SysLoginDTO;
import com.chensi.admin.web.dto.WebMenuDTO;
import com.chensi.admin.web.exception.TokenException;
import com.chensi.admin.web.service.SysLoginService;
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.crypto.hash.Sha256Hash;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.criteria.Predicate;
import java.util.*;

import static com.chensi.admin.web.common.BaseErrorCode.NOT_EXIST_USERNAME;

/**
 * @Author: si.chen
 * @Date: 2019/6/20 9:34
 */
@Service
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
public class SysLoginServiceImpl implements SysLoginService {

    private final WebUserRepository webUserRepository;

    private final ValidationUtil validationUtil;

    private final SysUserTokenRepository sysUserTokenRepository;

    private final WebMenuService webMenuService;

    private final WebUserService webUserService;

    private final RedisUtil redisUtil;

    @Autowired
    public SysLoginServiceImpl(WebUserRepository webUserRepository, ValidationUtil validationUtil, SysUserTokenRepository sysUserTokenRespository, WebMenuService webMenuService, WebUserService webUserService, RedisUtil redisUtil) {
        this.webUserRepository = webUserRepository;
        this.validationUtil = validationUtil;
        this.sysUserTokenRepository = sysUserTokenRespository;
        this.webMenuService = webMenuService;
        this.webUserService = webUserService;
        this.redisUtil = redisUtil;
    }

    @Override
    public SysUserToken find(SysUserToken sysUserToken) {
        return sysUserTokenRepository.findOne((root, query, cb) -> {
                    List<Predicate> list = new ArrayList<>();
                    if (StringUtils.isNotBlank(sysUserToken.getUserId())) {
                        Predicate predicate = cb.equal(root.get("userId"), sysUserToken.getUserId());
                        list.add(predicate);
                    }
                    if (StringUtils.isNotBlank(sysUserToken.getToken())) {
                        Predicate predicate = cb.equal(root.get("token"), sysUserToken.getToken());
                        list.add(predicate);
                    }
                    query.where(list.toArray(new Predicate[0]));
                    return query.getRestriction();
                }
        ).orElse(null);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public SysLoginDTO login(SysLoginDTO sysLoginDTO) throws BaseException {
        WebUser webUser = webUserRepository.findByUsername(sysLoginDTO.getUsername());
        //校验用户名
        if (webUser == null) {
            throw new BaseException(NOT_EXIST_USERNAME);
        }
        String password = new Sha256Hash(sysLoginDTO.getPassword(), webUser.getSalt()).toHex();
        //校验密码
        validationUtil.AssertPassword(webUser.getUsername(), webUser.getPassword(), password);
        //校验用户状态
        if (Constants.USER_STATE_LOCKED.equals(webUser.getState())) {
            throw new UserLockedException();
        }
        //清除错误次数上限
        String key = "user:" + webUser.getUsername() + ":errorCount";
        redisUtil.del(key);
        SysLoginDTO userLogin = createToken(webUser.getId());
        //TODO 执行shiro ream的doGetAuthenticationInfo方法
        Subject subject = SecurityUtils.getSubject();
        OAuth2Token token = new OAuth2Token(userLogin.getToken());
        subject.login(token);
        return userLogin;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void logout(String userId) {
        //删除user_token
        sysUserTokenRepository.deleteByUserId(userId);
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updatePassword(SysLoginDTO sysLoginDTO) throws BaseException {
        WebUser webUser = webUserRepository.getOne(sysLoginDTO.getUserId());
        //校验旧密码
        String password = new Sha256Hash(sysLoginDTO.getOldPassword(), webUser.getSalt()).toHex();
        validationUtil.AssertPassword(webUser.getUsername(), webUser.getPassword(), password);
        webUser.setPassword(new Sha256Hash(sysLoginDTO.getPassword(), webUser.getSalt()).toHex());
        webUserRepository.save(webUser);
    }

    @Override
    public Set<String> getUserPermissions(String userId) throws BaseException {
        Set<String> permsSet = new HashSet<>();
        //判断超级管理员
        WebUser webUser = webUserRepository.getOne(userId);
        if (StringUtils.equals(webUser.getUsername(), Constants.SUPER_ADMIN_USERNAME)) {
            List<WebMenuDTO> list = webMenuService.list();
            for (WebMenuDTO menu : list) {
                if (StringUtils.isNotBlank(menu.getPerms())) {
                    permsSet.add(menu.getPerms());
                }
            }
        } else {
            WebUser user = webUserService.get(userId);
            Set<WebMenu> webMenuSet = user.getWebRole().getWebMenus();
            for (WebMenu menu : webMenuSet) {
                if (StringUtils.isNotBlank(menu.getPerms())) {
                    permsSet.add(menu.getPerms());
                }
            }
        }
        return permsSet;
    }

    @Override
    public SysUserToken getUserToken(String token) throws TokenException {
        SysUserToken param = new SysUserToken();
        param.setToken(token);
        SysUserToken sysUserToken = this.find(param);
        if (sysUserToken == null || sysUserToken.getExpireTime().getTime() < System.currentTimeMillis()) {
            throw new TokenException();
        } else {
            return sysUserToken;
        }
    }

    /**
     * 生成Token
     */
    private SysLoginDTO createToken(String userId) throws BaseException {
        //生成一个token
        String token = TokenGenerator.generateValue();
        //当前时间
        Date now = new Date();
        //过期时间
        Date expireTime = new Date(now.getTime() + Constants.TOKEN_EXPIRE * 1000);
        //判断是否生成过token
        SysUserToken param = new SysUserToken();
        param.setUserId(userId);
        SysUserToken sysUserToken = this.find(param);
        if (sysUserToken == null) {
            sysUserToken = new SysUserToken();
            sysUserToken.setToken(token);
            sysUserToken.setUserId(userId);
            sysUserToken.setUpdateTime(now);
            sysUserToken.setExpireTime(expireTime);
            sysUserTokenRepository.save(sysUserToken);
        } else {
            sysUserToken.setToken(token);
            sysUserToken.setUpdateTime(now);
            sysUserToken.setExpireTime(expireTime);
            sysUserTokenRepository.save(sysUserToken);
        }
        SysLoginDTO sysLoginDTO = new SysLoginDTO();
        sysLoginDTO.setUserId(userId);
        sysLoginDTO.setToken(token);
        return sysLoginDTO;
    }
}
